/*
 *
 *  Connection Manager
 *
 *  Copyright (C) 2007-2009  Intel Corporation. All rights reserved.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <string.h>

#include <gdbus.h>

#include "connman.h"

#define	_DBG_NETWORK(fmt, arg...)	DBG(DBG_NETWORK, fmt, ## arg)

static unsigned int hidden_counter = 0;

enum network_state {			/* NB: order is important */
	NETWORK_STATE_IDLE,		/* base state */
	NETWORK_STATE_DISCONNECTING,	/* disconnect started */
	NETWORK_STATE_CONNECTING,	/* connect started */
	NETWORK_STATE_ASSOCIATING,	/* associate started */
	NETWORK_STATE_CONNECTED,	/* connected, ready for use */
};
static const char *state_names[] = {
	[NETWORK_STATE_IDLE]		= "IDLE",
	[NETWORK_STATE_DISCONNECTING]	= "DISCONNECTING",
	[NETWORK_STATE_CONNECTING]	= "CONNECTING",
	[NETWORK_STATE_ASSOCIATING]	= "ASSOCIATING",
	[NETWORK_STATE_CONNECTED]	= "CONNECTED",
};

struct connman_network {
	struct connman_element element;
	enum connman_network_type type;
	enum connman_network_protocol protocol;
	enum network_state state;
	connman_bool_t available;
	connman_bool_t registered;	/* registered in element hierarchy */
	connman_bool_t hidden;
	connman_uint8_t strength;
	connman_uint16_t frequency;
	enum connman_network_phymode phymode;
	int scangen;
	char *identifier;
	char *address;
	char *name;
	char *node;
	char *group;

	struct connman_network_driver *driver;
	void *driver_data;

	struct connman_device *device;

	struct {
		void *ssid;
		int ssid_len;
		char *mode;
		unsigned short channel;
		char *security;
		char *passphrase;
		char *authmode;
		unsigned int wep_key_len;
		connman_uint8_t wep_key_idx;
		connman_uint8_t wep_key_matter[MAX_WEP_KEYSPACE];
	} wifi;
};

static const char *type2string(enum connman_network_type type)
{
	switch (type) {
	case CONNMAN_NETWORK_TYPE_UNKNOWN:
	case CONNMAN_NETWORK_TYPE_VENDOR:
		break;
	case CONNMAN_NETWORK_TYPE_WIFI:
		return "wifi";
	case CONNMAN_NETWORK_TYPE_WIMAX:
		return "wimax";
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN:
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
		return "bluetooth";
	case CONNMAN_NETWORK_TYPE_CELLULAR:
		return "cellular";
	}

	return NULL;
}

static void append_wifi_ssid(DBusMessageIter *dict, void *arg)
{
	const struct connman_network *network = arg;

	dbus_message_iter_append_fixed_array(dict, DBUS_TYPE_BYTE,
	    &network->wifi.ssid, network->wifi.ssid_len);
}

static void append_network_string(DBusMessageIter *dict,
        struct connman_network *network, const char *property)
{
        const char *string;

        string = connman_network_get_string(network, property);
        if (string != NULL)
                connman_dbus_dict_append_variant(dict, property,
                        DBUS_TYPE_STRING, &string);
}

static DBusMessage *get_properties(DBusConnection *conn,
					DBusMessage *msg, void *data)
{
	struct connman_network *network = data;
	DBusMessage *reply;
	DBusMessageIter array, dict;
	connman_bool_t connected;

	if (__connman_security_check_privilege(msg,
					CONNMAN_SECURITY_PRIVILEGE_PUBLIC) < 0)
		return __connman_error_permission_denied(msg);

	reply = dbus_message_new_method_return(msg);
	if (reply == NULL)
		return NULL;

	dbus_message_iter_init_append(reply, &array);

	dbus_message_iter_open_container(&array, DBUS_TYPE_ARRAY,
			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

	if (network->device) {
		const char *path = connman_device_get_path(network->device);
		if (path != NULL)
			connman_dbus_dict_append_variant(&dict, "Device",
						DBUS_TYPE_OBJECT_PATH, &path);
	}

	if (network->address != NULL)
		connman_dbus_dict_append_variant(&dict, "Address",
					DBUS_TYPE_STRING, &network->address);

	if (network->name != NULL)
		connman_dbus_dict_append_variant(&dict, "Name",
					DBUS_TYPE_STRING, &network->name);

	connected = (network->state == NETWORK_STATE_CONNECTED);
	connman_dbus_dict_append_variant(&dict, "Connected",
				DBUS_TYPE_BOOLEAN, &connected);

	if (network->strength > 0)
		connman_dbus_dict_append_variant(&dict, "Strength",
					DBUS_TYPE_BYTE, &network->strength);

	if (network->frequency > 0)
		connman_dbus_dict_append_variant(&dict, "Frequency",
					DBUS_TYPE_UINT16, &network->frequency);

	if (network->wifi.ssid != NULL && network->wifi.ssid_len > 0)
		connman_dbus_dict_append_variant_array(&dict, "WiFi.SSID",
		    DBUS_TYPE_BYTE, append_wifi_ssid, network);

	if (network->wifi.mode != NULL)
		connman_dbus_dict_append_variant(&dict, "WiFi.Mode",
				DBUS_TYPE_STRING, &network->wifi.mode);

	if (network->wifi.channel > 0)
		connman_dbus_dict_append_variant(&dict, "WiFi.Channel",
				DBUS_TYPE_UINT16, &network->wifi.channel);

	if (network->phymode != CONNMAN_NETWORK_PHYMODE_UNDEF) {
		const char *str =
		    connman_network_get_phymode_name(network->phymode);
		connman_dbus_dict_append_variant(&dict, "WiFi.PhyMode",
				DBUS_TYPE_STRING, &str);
	}

	if (network->wifi.security != NULL)
		connman_dbus_dict_append_variant(&dict, "WiFi.Security",
				DBUS_TYPE_STRING, &network->wifi.security);

	if (network->wifi.authmode != NULL)
		connman_dbus_dict_append_variant(&dict, CONNMAN_WIFI_AUTHMODE,
				DBUS_TYPE_STRING, &network->wifi.authmode);

	if (__connman_security_check_privilege(msg,
		CONNMAN_SECURITY_PRIVILEGE_READ_SECRET) == 0) {
		if (network->wifi.passphrase != NULL)
			connman_dbus_dict_append_variant(&dict, "WiFi.Passphrase",
					DBUS_TYPE_STRING, &network->wifi.passphrase);
                append_network_string(&dict, network,
                        CONNMAN_WIFI_EAP_IDENTITY);
                append_network_string(&dict, network, CONNMAN_WIFI_EAP_EAP);
                append_network_string(&dict, network,
                        CONNMAN_WIFI_EAP_INNEREAP);
                append_network_string(&dict, network,
                        CONNMAN_WIFI_EAP_ANONYMOUSIDENTITY);
                append_network_string(&dict, network,
                        CONNMAN_WIFI_EAP_CLIENTCERT);
                append_network_string(&dict, network, CONNMAN_WIFI_EAP_CERTID);
                append_network_string(&dict, network,
                        CONNMAN_WIFI_EAP_PRIVATEKEY);
                append_network_string(&dict, network,
                        CONNMAN_WIFI_EAP_PRIVATEKEYPASSWORD);
                append_network_string(&dict, network, CONNMAN_WIFI_EAP_KEYID);
                append_network_string(&dict, network, CONNMAN_WIFI_EAP_CACERT);
                append_network_string(&dict, network,
                        CONNMAN_WIFI_EAP_CACERTID);
                append_network_string(&dict, network, CONNMAN_WIFI_EAP_PIN);
                append_network_string(&dict, network,
                        CONNMAN_WIFI_EAP_PASSWORD);
                append_network_string(&dict, network,
                        CONNMAN_WIFI_EAP_KEY_MGMT);
	}
	dbus_message_iter_close_container(&array, &dict);

	return reply;
}




static GDBusMethodTable network_methods[] = {
	{ "GetProperties", "",   "a{sv}", get_properties },
	{ },
};

static GDBusSignalTable network_signals[] = {
	{ "PropertyChanged", "sv" },
	{ },
};

static DBusConnection *connection;

static void append_networks(DBusMessageIter *iter, void *arg)
{
	__connman_element_list(arg, CONNMAN_ELEMENT_TYPE_NETWORK, iter);
}
static void emit_networks_signal(struct connman_device *device)
{
	connman_dbus_send_property_changed_array(
	    connman_device_get_path(device), CONNMAN_DEVICE_INTERFACE,
	    "Networks", DBUS_TYPE_OBJECT_PATH, append_networks, device);
}

static int register_interface(struct connman_element *element)
{
	struct connman_network *network = element->network;

	_DBG_NETWORK("element %p name %s", element, element->name);

	if (g_dbus_register_interface(connection, element->path,
					CONNMAN_NETWORK_INTERFACE,
					network_methods, network_signals,
					NULL, network, NULL) == FALSE) {
		connman_error("Failed to register %s network", element->path);
		return -EIO;
	}

	network->registered = TRUE;

	emit_networks_signal(network->device);

	return 0;
}

static void unregister_interface(struct connman_element *element)
{
	struct connman_network * network = element->network;

	_DBG_NETWORK("element %p name %s", element, element->name);

	network->registered = FALSE;

	emit_networks_signal(network->device);

	g_dbus_unregister_interface(connection, element->path,
						CONNMAN_NETWORK_INTERFACE);
}

connman_bool_t __connman_network_has_driver(struct connman_network *network)
{
	if (network == NULL || network->driver == NULL)
		return FALSE;

	return network->registered;
}

static GSList *driver_list = NULL;

static gint compare_priority(gconstpointer a, gconstpointer b)
{
	const struct connman_network_driver *driver1 = a;
	const struct connman_network_driver *driver2 = b;

	return driver2->priority - driver1->priority;
}

/**
 * connman_network_driver_register:
 * @driver: network driver definition
 *
 * Register a new network driver
 *
 * Returns: %0 on success
 */
int connman_network_driver_register(struct connman_network_driver *driver)
{
	_DBG_NETWORK("driver %p name %s", driver, driver->name);

	driver_list = g_slist_insert_sorted(driver_list, driver,
							compare_priority);

	return 0;
}

/**
 * connman_network_driver_unregister:
 * @driver: network driver definition
 *
 * Remove a previously registered network driver
 */
void connman_network_driver_unregister(struct connman_network_driver *driver)
{
	_DBG_NETWORK("driver %p name %s", driver, driver->name);

	driver_list = g_slist_remove(driver_list, driver);
}

static void network_destruct(struct connman_element *element)
{
	struct connman_network *network = element->network;

	_DBG_NETWORK("element %p name %s", element, element->name);

	g_free(network->wifi.ssid);
	g_free(network->wifi.mode);
	g_free(network->wifi.security);
	g_free(network->wifi.authmode);
	g_free(network->wifi.passphrase);

	g_free(network->group);
	g_free(network->node);
	g_free(network->name);
	g_free(network->address);
	g_free(network->identifier);
}

/**
 * connman_network_create:
 * @identifier: network identifier (for example an unique name)
 * @type: network type
 *
 * Allocate a new network of type #type and assign its #identifier.
 *
 * Returns: a newly-allocated #connman_network structure
 */
struct connman_network *connman_network_create(const char *identifier,
						enum connman_network_type type)
{
	struct connman_network *network;
	const char *str;
	char *temp;

	_DBG_NETWORK("identifier %s type %d", identifier, type);

	network = g_try_new0(struct connman_network, 1);
	if (network == NULL)
		return NULL;

	_DBG_NETWORK("network %p", network);

	__connman_element_initialize(&network->element);

	//temp = connman_dbus_encode_string(identifier);
	if (identifier == NULL) {
		temp = g_strdup_printf("hidden_%d", hidden_counter++);
		network->hidden = TRUE;
	} else
		temp = g_strdup(identifier);

	if (temp == NULL) {
		g_free(network);
		return NULL;
	}

	network->element.name = temp;
	network->element.type = CONNMAN_ELEMENT_TYPE_NETWORK;

	network->element.network = network;
	network->element.destruct = network_destruct;

	str = type2string(type);
	if (str != NULL)
		connman_element_set_string(&network->element, "Type", str);

	network->state      = NETWORK_STATE_IDLE;
	network->type       = type;
	network->identifier = g_strdup(temp);

	return network;
}

/**
 * connman_network_ref:
 * @network: network structure
 *
 * Increase reference counter of  network
 */
struct connman_network *connman_network_ref(struct connman_network *network)
{
	if (connman_element_ref(&network->element) == NULL)
		return NULL;

	return network;
}

/**
 * connman_network_unref:
 * @network: network structure
 *
 * Decrease reference counter of network
 */
void connman_network_unref(struct connman_network *network)
{
	connman_element_unref(&network->element);
}

const char *__connman_network_get_type(struct connman_network *network)
{
	return type2string(network->type);
}

/**
 * connman_network_set_error:
 * @network: network structure
 * @error: error code
 *
 * Mark a network error. The error is sent to the element which causes an
 * external signal.
 */
void connman_network_set_error(struct connman_network *network,
				enum connman_element_error error)
{
	connman_element_set_error(&network->element, error);
}

/**
 * connman_network_get_type:
 * @network: network structure
 *
 * Get type of network
 */
enum connman_network_type connman_network_get_type(struct connman_network *network)
{
	return network->type;
}

/**
 * connman_network_get_identifier:
 * @network: network structure
 *
 * Get identifier of network
 */
const char *connman_network_get_identifier(struct connman_network *network)
{
	return network->identifier;
}

/**
 * connman_network_get_path:
 * @network: network structure
 *
 * Get path name of network
 */
const char *connman_network_get_path(struct connman_network *network)
{
	return network->element.path;
}

/**
 * connman_network_set_index:
 * @network: network structure
 * @index: index number
 *
 * Set index number of network
 */
void connman_network_set_index(struct connman_network *network, int index)
{
	network->element.index = index;
}

/**
 * connman_network_get_index:
 * @network: network structure
 *
 * Get index number of network
 */
int connman_network_get_index(struct connman_network *network)
{
	return network->element.index;
}

/**
 * connman_network_set_protocol:
 * @network: network structure
 * @protocol: network protocol
 *
 * Change protocol of network
 */
void connman_network_set_protocol(struct connman_network *network,
					enum connman_network_protocol protocol)
{
	network->protocol = protocol;
}

/**
 * connman_network_set_group:
 * @network: network structure
 * @group: group name
 *
 * Set group name for automatic clustering
 */
void connman_network_set_group(struct connman_network *network,
							const char *group)
{
	switch (network->type) {
	case CONNMAN_NETWORK_TYPE_UNKNOWN:
	case CONNMAN_NETWORK_TYPE_VENDOR:
		return;
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN:
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
	case CONNMAN_NETWORK_TYPE_CELLULAR:
	case CONNMAN_NETWORK_TYPE_WIFI:
	case CONNMAN_NETWORK_TYPE_WIMAX:
		break;
	}

	_DBG_NETWORK("network %p group %s", network, group);

	if (g_strcmp0(network->group, group) == 0) {
		if (group != NULL)
			__connman_profile_update_network(network);
		return;
	}

	if (network->group != NULL) {
		__connman_profile_remove_network(network);

		g_free(network->group);
	}

	network->group = g_strdup(group);

	if (network->group != NULL)
		__connman_profile_add_network(network);
}

/**
 * connman_network_get_group:
 * @network: network structure
 *
 * Get group name for automatic clustering
 */
const char *connman_network_get_group(struct connman_network *network)
{
	return network->group;
}

const char *__connman_network_get_ident(struct connman_network *network)
{
	if (network->device == NULL)
		return NULL;

	return __connman_device_get_ident(network->device);
}

connman_bool_t __connman_network_get_weakness(struct connman_network *network)
{
	switch (network->type) {
	case CONNMAN_NETWORK_TYPE_UNKNOWN:
	case CONNMAN_NETWORK_TYPE_VENDOR:
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN:
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
	case CONNMAN_NETWORK_TYPE_CELLULAR:
	case CONNMAN_NETWORK_TYPE_WIMAX:
		break;
	case CONNMAN_NETWORK_TYPE_WIFI:
		if (network->strength > 0 && network->strength < 20)
			return TRUE;
		break;
	}

	return FALSE;
}

/**
 * connman_network_set_available:
 * @network: network structure
 * @available: availability state
 *
 * Change availability state of network (in range)
 */
int connman_network_set_available(struct connman_network *network,
						connman_bool_t available)
{
	_DBG_NETWORK("network %p available %d", network, available);

	if (network->available == available)
		return -EALREADY;

	network->available = available;

	return 0;
}

/**
 * connman_network_get_available:
 * @network: network structure
 *
 * Get network available setting
 */
connman_bool_t connman_network_get_available(struct connman_network *network)
{
	if (network->hidden == TRUE)
		return TRUE;

	return network->available;
}

static connman_bool_t
signal_state(struct connman_network *network)
{
	dbus_bool_t connected = (network->state == NETWORK_STATE_CONNECTED);
	return connman_dbus_send_property_changed_variant(network->element.path,
	    CONNMAN_NETWORK_INTERFACE, "Connected",
	    DBUS_TYPE_BOOLEAN, &connected);
}

static gboolean set_connected(gpointer user_data)
{
	struct connman_network *network = user_data;
	struct connman_device *device = network->device;
	struct connman_service *service;

	service = connman_service_lookup_from_network(network);

	switch (network->state) {
	case NETWORK_STATE_CONNECTED:
		/* NB: this kicks off L3 address configuration */
		__connman_device_set_current_network(device, network);

		/* XXX only do this if >1 addr config setup? */
		__connman_service_indicate_state(service,
		    CONNMAN_SERVICE_STATE_CONFIGURATION);
		break;
	case NETWORK_STATE_IDLE:
		if (__connman_device_get_current_network(device) != NULL) {
			connman_element_unregister_children(&network->element);

			/* NB: L3 address state cleared on last connection */
			__connman_device_set_current_network(device, NULL);
		}

		network->hidden = FALSE;	/* XXX */

		__connman_service_indicate_state(service,
		    CONNMAN_SERVICE_STATE_IDLE);
		break;
	default:
		break;
	}
	return FALSE;		/* NB: meant for g_idle_add usage */
}

static void state_change(struct connman_network *network,
    enum network_state state)
{
	_DBG_NETWORK("network state change (%p) %s -> %s", network,
	    state_names[network->state], state_names[state]);
	network->state = state;
}

static void invalid_state(const struct connman_network *network,
    enum network_state state)
{
	connman_error("%s: INVALID network state change (%p), %s -> %s",
	    connman_device_get_interface(network->device), network,
	    state_names[network->state], state_names[state]);
}

/**
 * connman_network_get_connected:
 * @network: network structure
 *
 * Get network connection status
 */
connman_bool_t connman_network_get_connected(struct connman_network *network)
{
	return (network->state == NETWORK_STATE_CONNECTED);
}

/**
 * connman_network_set_connected:
 * @network: network structure
 *
 * Mark network connected.
 */
int connman_network_set_connected(struct connman_network *network)
{
	_DBG_NETWORK("network %p state %s", network,
	    state_names[network->state]);

	if (network->state == NETWORK_STATE_CONNECTED)
		return -EALREADY;
	/* NB: we do not reject invalid state changes */

	state_change(network, NETWORK_STATE_CONNECTED);

	if (network->registered == FALSE) {
		_DBG_NETWORK("not registered; defer");
		/* XXX no signal sent, intentional? */
		g_idle_add(set_connected, network);
	} else {
		signal_state(network);
		set_connected(network);
	}
	return 0;
}

/**
 * connman_network_set_connecting:
 * @network: network structure
 *
 * Mark network connecting.
 */
int connman_network_set_connecting(struct connman_network *network)
{
	struct connman_service *service;

	_DBG_NETWORK("network %p state %s", network,
	    state_names[network->state]);

	/*
	 * Even if the network state doesn't change, we may stiil
	 * need to change the service state below
	 */
	if (network->state != NETWORK_STATE_CONNECTING)
		state_change(network, NETWORK_STATE_CONNECTING);

	/* NB: don't signal state change; let it happen later */

	service = connman_service_lookup_from_network(network);
	__connman_service_indicate_state(service,
	    CONNMAN_SERVICE_STATE_ASSOCIATION);
	return 0;
}

int connman_network_restart_connect_timer(struct connman_network *network)
{
	struct connman_service *service;

	_DBG_NETWORK("network %s state %s", network->name,
	    state_names[network->state]);

	service = connman_service_lookup_from_network(network);
	if (service != NULL)
		__connman_service_restart_connect_timer(service);
	return 0;
}

/**
 * __connman_network_connect:
 * @network: network structure
 *
 * Connect network.
 */
int __connman_network_connect(struct connman_network *network)
{
	int err;

	_DBG_NETWORK("network %p state %s", network,
	    state_names[network->state]);

	if (network->state == NETWORK_STATE_CONNECTED)
		return -EISCONN;
	if (network->state >= NETWORK_STATE_CONNECTING)
		return -EALREADY;
	if (network->driver == NULL)
		return -EUNATCH;
	if (network->driver->connect == NULL)
		return -ENOSYS;

	/* NB: may be DISCONNECTING; driver must handle? */

	state_change(network, NETWORK_STATE_CONNECTING);

	err = network->driver->connect(network);
	if (err == 0) {
		state_change(network, NETWORK_STATE_CONNECTED);
		set_connected(network);
	} else if (err != -EINPROGRESS) {
		state_change(network, NETWORK_STATE_IDLE);
		network->hidden = FALSE;		/* XXX? */
	}
	return err;
}

/**
 * connman_network_get_associating:
 * @network: network structure
 *
 * Get network associating status
 */
connman_bool_t connman_network_get_associating(struct connman_network *network)
{
	return (network->state == NETWORK_STATE_ASSOCIATING);
}

/**
 * connman_network_set_associating:
 * @network: network structure
 *
 * Mark network in associating state.
 */
int connman_network_set_associating(struct connman_network *network)
{
	struct connman_service *service;

	_DBG_NETWORK("network %p state %s", network,
	    state_names[network->state]);

	if (network->state == NETWORK_STATE_ASSOCIATING)
		return -EALREADY;
	/* NB: CONNECTED -> ASSOCIATING is valid */
	/* NB: IDLE -> ASSOCIATING is valid; fix supplicant? */
	if (network->state == NETWORK_STATE_DISCONNECTING) {
		invalid_state(network, NETWORK_STATE_ASSOCIATING);
		return -EINVAL;
	}

	state_change(network, NETWORK_STATE_ASSOCIATING);

	service = connman_service_lookup_from_network(network);
	__connman_service_indicate_state(service,
	    CONNMAN_SERVICE_STATE_ASSOCIATION);
	return 0;
}

/**
 * connman_network_set_disconnecting:
 * @network: network structure
 *
 * Mark network disconnecting.
 */
int connman_network_set_disconnecting(struct connman_network *network)
{
	_DBG_NETWORK("network %p state %s", network,
	    state_names[network->state]);

	if (network->state <= NETWORK_STATE_DISCONNECTING)
		return -EALREADY;

	state_change(network, NETWORK_STATE_DISCONNECTING);
	return 0;
}

/**
 * connman_network_set_disconnected_only:
 * @network: network structure
 *
 * Mark network (only) disconnected.
 */
int connman_network_set_disconnected_only(struct connman_network *network)
{
	_DBG_NETWORK("network %p state %s%s", network,
	    state_names[network->state],
	    network->hidden == TRUE ? " (hidden)" : "");

	if (network->state == NETWORK_STATE_IDLE)
		return -EALREADY;

	state_change(network, NETWORK_STATE_IDLE);

	if (network->hidden == TRUE) {
		/*
		 * Hack.  A network is hidden when it is created without
		 * ever seeing it (e.g. due to a dbus request).  These
		 * networks are useful only to get a connect request
		 * started after which they are relegated to the scrap
		 * heap when the real network is located or the request
		 * times out.  They cannot be used again so when they are
		 * marked disconnected we remove it from the device which
		 * causes it to be reclaimed (and any associated service
		 * that would otherwise hang around solely attached to
		 * the network).
		 */
		network->hidden = FALSE;   /* NB: no need but do it anyway */
		connman_device_remove_network(network->device,
		    network->identifier);
	}

	if (network->registered == TRUE)
		signal_state(network);
	return 0;
}

/**
 * connman_network_set_disconnected:
 * @network: network structure
 *
 * Mark network disconnected and update associated service and
 * device to reflect network state change.
 */
int connman_network_set_disconnected(struct connman_network *network)
{
	int err;

	_DBG_NETWORK("network %p state %s", network,
	    state_names[network->state]);

	/* NB: hold a ref as the network may be reclaimed */
	(void) connman_network_ref(network);

	err = connman_network_set_disconnected_only(network);
	if (err == 0 && network->registered == TRUE)
		set_connected(network);

	connman_network_unref(network);

	return err;
}

/**
 * __connman_network_disconnect:
 * @network: network structure
 *
 * Disconnect network.
 */
int __connman_network_disconnect(struct connman_network *network)
{
	int err;

	_DBG_NETWORK("network %p state %s", network,
	    state_names[network->state]);

	if (network->state == NETWORK_STATE_IDLE)
		return -ENOTCONN;
	if (network->state == NETWORK_STATE_DISCONNECTING)
		return -EALREADY;
	if (network->driver == NULL)
		return -EUNATCH;
	if (network->driver->disconnect == NULL)
		return -ENOSYS;

	state_change(network, NETWORK_STATE_DISCONNECTING);

	err = network->driver->disconnect(network);
	if (err == 0)
		connman_network_set_disconnected(network);	/* XXX? */
	return err;
}

/**
 * connman_network_get_in_use:
 * @network: network structure
 *
 * Get whether network is connecting, associating, or connected.
 */
connman_bool_t connman_network_get_in_use(struct connman_network *network)
{
	return (network->state >= NETWORK_STATE_CONNECTING);
}

/**
 * connman_network_get_previously_connected:
 * @network: network structure
 *
 * Return whether there is an associated service that is marked as previously
 * connected.
 */
connman_bool_t connman_network_get_previously_connected(
						struct connman_network *network)
{
	struct connman_service *service =
	    connman_service_lookup_from_network(network);

	if (service == NULL)
		return FALSE;
	return connman_service_get_previously_connected(service);
}

/**
 * connman_network_set_address:
 * @network: network structure
 * @address: binary address value
 * @size: binary address length
 *
 * Set unique address value for network
 */
int connman_network_set_address(struct connman_network *network,
	const void *address, unsigned int size)
{
	const unsigned char *addr_octet = address;
	char buf[20];

	_DBG_NETWORK("network %p size %d", network, size);

	if (size != 6) {
		connman_error("%s: bad size %d", __func__, size);
		return -EINVAL;
	}

	g_snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X",
	    addr_octet[0], addr_octet[1], addr_octet[2],
	    addr_octet[3], addr_octet[4], addr_octet[5]);
	return connman_network_set_string(network, "Address", buf);
}

/**
 * connman_network_set_name:
 * @network: network structure
 * @name: name value
 *
 * Set display name value for network
 */
int connman_network_set_name(struct connman_network *network, const char *name)
{
	_DBG_NETWORK("network %p name %s", network, name);

	g_free(network->name);
	network->name = g_strdup(name);
	if (network->name == NULL) {
		connman_error("%s: no memory", __func__);
		return -ENOMEM;
	}
	return connman_element_set_string(&network->element, "Name", name);
}

/**
 * connman_network_get_strength:
 * @network: network structure
 *
 * Return signal strength value for network
 */
connman_uint8_t connman_network_get_strength(struct connman_network *network)
{
	return network->strength;
}

/**
 * connman_network_set_strength:
 * @network: network structure
 * @strength: strength value
 *
 * Set signal strength value for network
 */
int connman_network_set_strength(struct connman_network *network,
    connman_uint8_t strength)
{
	_DBG_NETWORK("network %p strength %d", network, strength);

	network->strength = strength;
	__connman_service_update_strength(network);

	return 0;
}

/**
 * connman_network_set_string:
 * @network: network structure
 * @key: unique identifier
 * @value: string value
 *
 * Set string value for specific key
 */
int connman_network_set_string(struct connman_network *network,
    const char *key, const char *value)
{
	_DBG_NETWORK("network %p key %s value %s", network, key,
		     connman_log_get_masked_value(key, value));

	if (g_strcmp0(key, "Name") == 0)
		return connman_network_set_name(network, value);

	if (g_str_equal(key, "Address") == TRUE) {
		g_free(network->address);
		network->address = g_strdup(value);
	} else if (g_str_equal(key, "Node") == TRUE) {
		g_free(network->node);
		network->node = g_strdup(value);
	} else if (g_str_equal(key, "WiFi.Mode") == TRUE) {
		g_free(network->wifi.mode);
		network->wifi.mode = g_strdup(value);
	} else if (g_str_equal(key, "WiFi.Security") == TRUE) {
		g_free(network->wifi.security);
		network->wifi.security = g_strdup(value);
	} else if (g_str_equal(key, CONNMAN_WIFI_AUTHMODE) == TRUE) {
		g_free(network->wifi.authmode);
		network->wifi.authmode = g_strdup(value);
	} else if (g_str_equal(key, "WiFi.Passphrase") == TRUE) {
		g_free(network->wifi.passphrase);
		network->wifi.passphrase = g_strdup(value);
	}

	return connman_element_set_string(&network->element, key, value);
}

/**
 * connman_network_get_string:
 * @network: network structure
 * @key: unique identifier
 *
 * Get string value for specific key
 */
const char *connman_network_get_string(struct connman_network *network,
    const char *key)
{
	_DBG_NETWORK("network %p key %s", network, key);

	if (g_str_equal(key, "Address") == TRUE)
		return network->address;
	else if (g_str_equal(key, "Name") == TRUE)
		return network->name;
	else if (g_str_equal(key, "Node") == TRUE)
		return network->node;
	else if (g_str_equal(key, "WiFi.Mode") == TRUE)
		return network->wifi.mode;
	else if (g_str_equal(key, "WiFi.Security") == TRUE)
		return network->wifi.security;
	else if (g_str_equal(key, CONNMAN_WIFI_AUTHMODE) == TRUE)
		return network->wifi.authmode;
	else if (g_str_equal(key, "WiFi.Passphrase") == TRUE)
		return network->wifi.passphrase;

	return connman_element_get_string(&network->element, key);
}

/**
 * connman_network_set_uint8:
 * @network: network structure
 * @key: unique identifier
 * @value: integer value
 *
 * Set integer value for specific key
 */
int connman_network_set_uint8(struct connman_network *network,
    const char *key, connman_uint8_t value)
{
	_DBG_NETWORK("network %p key %s value %d", network, key, value);

	if (g_str_equal(key, "WiFi.WEPKeyIndex") == TRUE) {
		network->wifi.wep_key_idx = value;
		return 0;
	}
	return connman_element_set_uint8(&network->element, key, value);
}

/**
 * connman_network_get_uint8:
 * @network: network structure
 * @key: unique identifier
 *
 * Get integer value for specific key
 */
connman_uint8_t connman_network_get_uint8(struct connman_network *network,
    const char *key)
{
	_DBG_NETWORK("network %p key %s", network, key);

	if (g_str_equal(key, "WiFi.WEPKeyIndex") == TRUE)
		return network->wifi.wep_key_idx;
	return connman_element_get_uint8(&network->element, key);
}

/**
 * connman_network_set_uint16:
 * @network: network structure
 * @key: unique identifier
 * @value: integer value
 *
 * Set integer value for specific key
 */
int connman_network_set_uint16(struct connman_network *network,
    const char *key, connman_uint16_t value)
{
	_DBG_NETWORK("network %p key %s value %d", network, key, value);

	if (g_str_equal(key, "Frequency") == TRUE) {
		network->frequency = value;
		return 0;
	}
	if (g_str_equal(key, "WiFi.Channel") == TRUE) {
		network->wifi.channel = value;
		return 0;
	}
	return -EINVAL;
}

/**
 * connman_network_get_uint16:
 * @network: network structure
 * @key: unique identifier
 *
 * Get integer value for specific key
 */
connman_uint16_t connman_network_get_uint16(struct connman_network *network,
    const char *key)
{
	_DBG_NETWORK("network %p key %s", network, key);

	if (g_str_equal(key, "Frequency") == TRUE)
		return network->frequency;
	else if (g_str_equal(key, "WiFi.Channel") == TRUE)
		return network->wifi.channel;

	return 0;
}

/**
 * connman_network_set_phymode:
 * @network: network structure
 * @phymode: WiFi phy mode
 *
 * Change WiFi phy mode number of network.
 */
void connman_network_set_phymode(struct connman_network *network,
    enum connman_network_phymode phymode)
{
	network->phymode = phymode;
}

/**
 * connman_network_get_phymode:
 * @network: network structure
 *
 * Get WiFi phy mode
 */
enum connman_network_phymode connman_network_get_phymode(struct connman_network
    *network)
{
	return network->phymode;
}

const char *connman_network_get_phymode_name(enum connman_network_phymode phymode)
{
	static const char *phymodes[] = {
		[CONNMAN_NETWORK_PHYMODE_UNDEF] = "Undef",
		[CONNMAN_NETWORK_PHYMODE_11A]	= "802.11a",
		[CONNMAN_NETWORK_PHYMODE_11B]	= "802.11b",
		[CONNMAN_NETWORK_PHYMODE_11G]	= "802.11g",
		[CONNMAN_NETWORK_PHYMODE_11N]	= "802.11n",
		[CONNMAN_NETWORK_PHYMODE_HALF]	= "PSB Half-width",
		[CONNMAN_NETWORK_PHYMODE_QUARTER] = "PSB Quarter-width",
		[CONNMAN_NETWORK_PHYMODE_TURBO]	= "Atheros Turbo mode",
	};
	return phymodes[phymode < CONNMAN_NETWORK_PHYMODE_MAX ?
	    phymode : CONNMAN_NETWORK_PHYMODE_UNDEF];
}

/**
 * connman_network_set_scangen:
 * @network: network structure
 * @scangen: scan generation number
 *
 * Change scan generation number of network
 */
void connman_network_set_scangen(struct connman_network *network, int scangen)
{
	network->scangen = scangen;
}

/**
 * connman_network_get_scangen:
 * @network: network structure
 *
 * Get scan generation number of network
 */
int connman_network_get_scangen(struct connman_network *network)
{
	return network->scangen;
}

/**
 * connman_network_set_blob:
 * @network: network structure
 * @key: unique identifier
 * @data: blob data
 * @size: blob size
 *
 * Set binary blob value for specific key
 */
int connman_network_set_blob(struct connman_network *network,
    const char *key, const void *data, unsigned int size)
{
	_DBG_NETWORK("network %p key %s size %d", network, key, size);

	if (g_strcmp0(key, "Address") == 0)
		return connman_network_set_address(network, data, size);

	if (g_str_equal(key, "WiFi.SSID") == TRUE) {
		g_free(network->wifi.ssid);
		network->wifi.ssid = g_try_malloc(size);
		if (network->wifi.ssid == NULL && size != 0) {
			network->wifi.ssid_len = 0;
			return -ENOMEM;
		}

		memcpy(network->wifi.ssid, data, size);
		network->wifi.ssid_len = size;
		return 0;
	}

	if (g_str_equal(key, "WiFi.WEPKey") == TRUE) {
		if (size > MAX_WEP_KEYSPACE)
			return -EINVAL;
		memcpy(network->wifi.wep_key_matter, data, size);
		network->wifi.wep_key_len = size;
		return 0;
	}

	return connman_element_set_blob(&network->element, key, data, size);
}

/**
 * connman_network_get_blob:
 * @network: network structure
 * @key: unique identifier
 * @size: pointer to blob size
 *
 * Get binary blob value for specific key
 */
const void * connman_network_get_blob(struct connman_network *network,
    const char *key, unsigned int *size)
{
	_DBG_NETWORK("network %p key %s", network, key);

	if (g_str_equal(key, "WiFi.SSID") == TRUE) {
		if (size != NULL)
			*size = network->wifi.ssid_len;
		return network->wifi.ssid;
	}

	if (g_str_equal(key, "WiFi.WEPKey") == TRUE) {
		*size = network->wifi.wep_key_len;
		return network->wifi.wep_key_matter;
	}

	return connman_element_get_blob(&network->element, key, size);
}

/**
 * connman_network_get_hex_ssid:
 * @network: network structure
 *
 * Get hex SSID value for network or NULL if one does not exist, or is too
 * long
 */
char *connman_network_get_hex_ssid(struct connman_network *network)
{
	unsigned char *ssid = network->wifi.ssid;
	int ssid_len = network->wifi.ssid_len;
	char string_buf[MAX_SSID_LENGTH*2+1];
	char *string_ptr;
	int i;

	if (ssid == NULL || ssid_len <= 0 || ssid_len > MAX_SSID_LENGTH ||
	    ssid[0] == '\0')
		return NULL;

	for (i = 0, string_ptr = string_buf; i < ssid_len; i++, string_ptr += 2)
		snprintf(string_ptr, sizeof(string_buf)-(string_ptr-string_buf),
			 "%02x", ssid[i]);

	return g_strdup(string_buf);
}

void __connman_network_set_device(struct connman_network *network,
	struct connman_device *device)
{
	network->device = device;
}

/**
 * connman_network_get_device:
 * @network: network structure
 *
 * Get parent device of network
 */
struct connman_device *connman_network_get_device(
    struct connman_network *network)
{
	return network->device;
}

/**
 * connman_network_get_data:
 * @network: network structure
 *
 * Get private network data pointer
 */
void *connman_network_get_data(struct connman_network *network)
{
	return network->driver_data;
}

/**
 * connman_network_set_data:
 * @network: network structure
 * @data: data pointer
 *
 * Set private network data pointer
 */
void connman_network_set_data(struct connman_network *network, void *data)
{
	network->driver_data = data;
}

/**
 * connman_network_set_operator:
 * @network: network structure
 * @op: network operator structire
 *
 * Set operator information (name, operator code, country, etc) for the network
 */
void connman_network_set_operator(struct connman_network *network,
                                  struct connman_network_operator *op)
{
	struct connman_service *service =
		connman_service_lookup_from_network(network);

	if (service != NULL)
		__connman_service_set_operator(service, op);
}

/**
 * connman_network_set_registration_info:
 * @network: network structure
 * @network_technology: e.g., "GPRS", "EDGE", "1xRTT", "EVDO", etc.
 * @roaming_state: e.g., "home", "roaming", etc.
 *
 * Set network technology and roaming status information for the network.
 */
void connman_network_set_registration_info(
		struct connman_network *network,
		enum connman_network_cellular_technology network_technology,
		enum connman_network_cellular_roaming_state roaming_state)
{
	struct connman_service *service =
		connman_service_lookup_from_network(network);

	if (service != NULL)
		__connman_service_set_registration_info(service,
							network_technology,
							roaming_state);
}

/**
 * connman_network_set_activation_state:
 * @network: network structure
 * @activation state: control interface
 * @err: if the activation failed a reason for the failure
 *
 * Set the activation state of the network
 */
void connman_network_set_activation_state(struct connman_network *network,
	enum connman_network_activation_state activation_state,
	enum connman_element_error err)
{
	struct connman_service *service =
		connman_service_lookup_from_network(network);

	if (service != NULL)
		__connman_service_set_activation_state(service,
						       activation_state, err);

}

/**
 * connman_network_set_olp_url:
 * @network: network structure
 * @olp_url: online payment url
 *
 * Set online payment url for the network
 */
void connman_network_set_olp_url(struct connman_network *network,
				 const char *olp_url,
				 const char *olp_method,
				 const char *olp_postdata)
{
	struct connman_service *service =
		connman_service_lookup_from_network(network);

	if (service != NULL)
		__connman_service_set_olp_url(service, olp_url, olp_method, olp_postdata);
}

/**
 * connman_network_set_usage_url:
 * @network: network structure
 * @usage_url: data usage url
 *
 * Set data usage url for the network
 */
void connman_network_set_usage_url(struct connman_network *network,
				   const char *usage_url)
{
	struct connman_service *service =
		connman_service_lookup_from_network(network);

	if (service != NULL)
		__connman_service_set_usage_url(service, usage_url);
}

/**
 * __connman_network_activate_cellular_modem
 * @network: network structure
 * @carrier: the carrier (CDMA) to activate
 *
 * Returns: -EINVAL if network type does not support activation
 */
int __connman_network_activate_cellular_modem(
	struct connman_network *network,
	const char *carrier
	)
{
	if (network->driver->activate_cellular_network != NULL)
		return network->driver->activate_cellular_network(network,
								  carrier);
	else
		return -EINVAL;
}

const struct connman_network_apn *connman_network_get_apn(
		struct connman_network *network)
{
	struct connman_service *service =
		connman_service_lookup_from_network(network);

	if (service != NULL)
		return __connman_service_get_apn(service);
	else {
		connman_warn("In connman_network_get_apn: No service found for"
			     " network %s", network->identifier);
		return NULL;
	}
}

void connman_network_save_last_good_apn(struct connman_network *network,
				const struct connman_network_apn *apn)
{
	struct connman_service *service =
		connman_service_lookup_from_network(network);

	if (service != NULL)
		__connman_service_save_last_good_apn(service, apn);
}

const struct connman_network_apn *connman_network_get_last_good_apn(
		struct connman_network *network)
{
	struct connman_service *service =
		connman_service_lookup_from_network(network);

	if (service == NULL)
		return NULL;
	return connman_service_get_last_good_apn(service);
}

struct peer_count_state {
	struct connman_network *exclude;
	int peer_count;
};

static void match_peer_network(struct connman_element *element,
	gpointer user_data)
{
	struct connman_network *network = (struct connman_network *) element;
	struct peer_count_state *state = user_data;

	if (network == state->exclude)
		return;
	if (g_strcmp0(state->exclude->group, network->group) != 0)
		return;

	state->peer_count++;
}

int connman_network_get_peer_count(struct connman_network *network) {
	struct peer_count_state state;

	state.exclude = network;
	state.peer_count = 0;

	__connman_element_foreach(NULL, CONNMAN_ELEMENT_TYPE_NETWORK,
	    match_peer_network, GUINT_TO_POINTER(&state));

	return state.peer_count;
}

enum connman_network_cellular_roaming_state
	connman_network_get_roaming_state(struct connman_network *network)
{
	struct connman_service *service =
		connman_service_lookup_from_network(network);

	if (service == NULL)
		return CONNMAN_NETWORK_ROAMING_STATE_UNKNOWN;
	return __connman_service_get_roaming_state(service);
}

struct match_state {
	struct connman_network *exclude;
	struct connman_network *result;
};

static void match_other_network(struct connman_element *element,
    gpointer user_data)
{
	struct connman_network *network = (struct connman_network *) element;
	struct match_state *state = user_data;

	if (network == state->exclude)
		return;
	if (g_strcmp0(state->exclude->group, network->group) != 0)
		return;

	if (state->result == NULL) {
		state->result = network;
		return;
	}

	/* prefer network with highest signal strength */
	if (state->result->strength < network->strength)
		state->result = network;
}

/*
 * Find the best network to replace the specified network by
 * by matching the network group and selecting the network
 * with the strongest signal.  This is used to update the
 * preferred network bound to a service when the current
 * preferred network is removed (e.g. because it goes out of range).
 */
struct connman_network *__connman_network_find_other_network(
    struct connman_network *network)
{
	struct match_state state;

	state.exclude = network;
	state.result = NULL;

	__connman_element_foreach(NULL, CONNMAN_ELEMENT_TYPE_NETWORK,
	    match_other_network, GUINT_TO_POINTER(&state));

	return state.result;
}

static gboolean match_driver(struct connman_network *network,
    struct connman_network_driver *driver)
{
	if (network->type == driver->type ||
			driver->type == CONNMAN_NETWORK_TYPE_UNKNOWN)
		return TRUE;

	return FALSE;
}

static int network_probe(struct connman_element *element)
{
	struct connman_network *network = element->network;
	GSList *list;
	int err;

	_DBG_NETWORK("element %p name %s", element, element->name);

	if (network == NULL)
		return -ENODEV;

	for (list = driver_list; list; list = list->next) {
		struct connman_network_driver *driver = list->data;

		if (match_driver(network, driver) == FALSE)
			continue;

		_DBG_NETWORK("driver %p name %s", driver, driver->name);

		if (driver->probe(network) == 0) {
			network->driver = driver;
			break;
		}
	}

	if (network->driver == NULL)
		return -ENODEV;

	err = register_interface(element);
	if (err < 0) {
		if (network->driver->remove)
			network->driver->remove(network);
		return err;
	}

	switch (network->type) {
	case CONNMAN_NETWORK_TYPE_UNKNOWN:
	case CONNMAN_NETWORK_TYPE_VENDOR:
	case CONNMAN_NETWORK_TYPE_CELLULAR:
		/*
		 * CELLULAR has been added to the network types where
		 * we do not call __connman_profile_add_network,
		 * because otherwise the service object ends up with a
		 * reference count of two and never disappears even
		 * after unplugging the modem.
		 * __connman_profile_add_network was already called
		 * from connman_network_set_group.  It is unclear why
		 * other NETWORK_TYPES do not need this fix.
		 */
		break;
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN:
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
	case CONNMAN_NETWORK_TYPE_WIFI:
	case CONNMAN_NETWORK_TYPE_WIMAX:
		if (network->group != NULL)
			__connman_profile_add_network(network);
		break;
	}

	return 0;
}

static void network_remove(struct connman_element *element)
{
	struct connman_network *network = element->network;

	_DBG_NETWORK("element %p name %s", element, element->name);

	if (network == NULL)
		return;

	if (network->driver == NULL)
		return;

	switch (network->type) {
	case CONNMAN_NETWORK_TYPE_UNKNOWN:
	case CONNMAN_NETWORK_TYPE_VENDOR:
		break;
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_PAN:
	case CONNMAN_NETWORK_TYPE_BLUETOOTH_DUN:
	case CONNMAN_NETWORK_TYPE_CELLULAR:
	case CONNMAN_NETWORK_TYPE_WIFI:
	case CONNMAN_NETWORK_TYPE_WIMAX:
		if (network->group != NULL) {
			__connman_profile_remove_network(network);

			g_free(network->group);
			network->group = NULL;
		}
		break;
	}

	unregister_interface(element);

	if (network->driver->remove)
		network->driver->remove(network);
}

static void network_change(struct connman_element *element)
{
	struct connman_network *network = element->network;

	_DBG_NETWORK("element %p name %s", element, element->name);

	if (element->state == CONNMAN_ELEMENT_STATE_ERROR &&
	    element->error == CONNMAN_ELEMENT_ERROR_DHCP_FAILED &&
	    network->state == NETWORK_STATE_CONNECTED) {
		/*
		 * DHCP failed, tear down state.  Clear out children
		 * (XXX connection?), mark device no longer connected
		 * (XXX wrong?) and disconnect network.
		 */
		connman_element_unregister_children(element);
		/* XXX wrong for wifi, schedules scan for after disconnect */
		connman_device_set_connected(network->device, FALSE);

		__connman_network_disconnect(network);
	}
}

/**
 * connman_network_get_name_from_id
 * @id: character buffer containing id
 * @id_len: length of ID
 * @result: pointer to buf for result to be written, or NULL to allocate one
 * @result_size: size of result
 *
 * Returns: Pointer to result buffer, which will be a null-terminated string,
 *          or NULL if result was passed in NULL and malloc fails, or if the
 *          passed in result_size is too small.
 */
char *connman_network_get_name_from_id(const unsigned char *id,
				       unsigned int id_len,
				       char *result,
				       unsigned int result_size)
{
	unsigned int i;

	if (result == NULL) {
		result = g_try_malloc(id_len+1);
		if (result == NULL)
			return NULL;
	} else if (id_len >= result_size) {
		connman_error("%s: bad result_size %d", __func__, result_size);
		return NULL;
	}

	/*
	 * TODO(pstew):  We could use g_utf8_validate() to test whether
	 * the string is valid UTF-8, however nobody downstream (python
	 * utils or chrome) correctly handle UTF-8 strings yet.
	 */

	for (i = 0; i < id_len; i++) {
		/*
		 * NB: Filter using g_ascii_isprint so that all
		 * non-ASCII-printable characters are removed.  This ensures
		 * that the resulting string will be clean as far as DBus
		 * strings are concerned, and current versions of downstream
		 * consumers such as Chrome won't do silly things with the
		 * results.
		 */
		if (g_ascii_isprint(id[i]))
			result[i] = id[i];
		else
			result[i] = '?';
	}
	result[id_len] = '\0';

	return result;
}


static struct connman_driver network_driver = {
	.name		= "network",
	.type		= CONNMAN_ELEMENT_TYPE_NETWORK,
	.priority	= CONNMAN_DRIVER_PRIORITY_LOW,
	.probe		= network_probe,
	.remove		= network_remove,
	.change		= network_change,
};

int __connman_network_init(void)
{
	_DBG_NETWORK("");

	connection = connman_dbus_get_connection();

	return connman_driver_register(&network_driver);
}

void __connman_network_cleanup(void)
{
	_DBG_NETWORK("");

	connman_driver_unregister(&network_driver);

	dbus_connection_unref(connection);
}
